#!/usr/bin/env python
# Tkinter GUI for pyDSL2Sqlite converter
#
#
# TODO: fix SQLite database file location DONE
# TODO: add Cancel job DONE
# TODO: process Enter in input field DONE
# TODO: Add buttons: Start/STOP webserver (as daemon), Open in Browser DONE
# TODO: Display status of webserver DONE untested
# TODO: Optparse: port config, dicts .db files in INI file IN PROGRESS DONE
# TODO: Option to start server automatically (startup) IN PROGRESS
# TODO: Option to stop server by calling http://server:port/quit
# TODO: Get default # results per page from config
# FIXME: "Job already in progress" after conversion completed
# FIXME: Add validation for charactes in table name (only [a-z])
# FIXME: Add logic to make sure webserver is stopped before starting import
# FIXME: PREVENT STARTING MORE THAN 1 WEBSERVER
# FIXME: PREVENT starting IMPORT if SERVER IS UP or stop server before import !!!!!!!!!!!!!!
# FIXED: server check stays on after UI is closed use .after instead of thread;
# TODO: Use a single table name for all databases; also modify dict4.py

import os
import sys
from Tkinter import *
import tkMessageBox
import tkSimpleDialog
import tkFileDialog
import tkColorChooser

import threading
import time
import subprocess
import webbrowser
import ConfigParser
import urllib

conf = ConfigParser.ConfigParser()
conf.read('pywords.ini')
srv_host = conf.get('server', 'server_host')
srv_port = conf.get('server', 'server_port')
srv_path = conf.get('server', 'server_path')
#db_path = conf.get('database', 'sqlite_db_path')
#db_file = conf.get('database', 'sqlite_db_file')
db_dir = conf.get('database', 'sqlite_db_dir')
enc = conf.get('converter', 'encodings')
encodings = enc.split() or []

SRV_POLL_PERIOD = 5000 # ping server every NN milliseconds
SRV_STATE_RUNNING = 1
SRV_STATE_DOWN = 0
SRV_STATE_UNKNOWN = -1
srv_status = SRV_STATE_UNKNOWN

# SERVER_URL = 'http://localhost:8778/cgi-bin/dict3.py'
SERVER_URL = 'http://%s:%s/%s' % (srv_host, srv_port, srv_path)
#sqlite_db_full_path = os.path.join(db_path, db_file)

root = Tk()
root.title('DSL Converter')
root.resizable(0,0)

globalStatus = 'Idle'
globalStop = False
job = None

dslFormats = [
        ('Lingvo DSL source','*.dsl'),
        ('Plain text','*.txt'),
        ('All Files','*.*'),
]

sqliteFormats = [
        ('SQLite database',('*.sqlite', '*.sqlite3', '*.db')),
        ('All Files','*.*'),
]

def destroyApp():
        if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
                if job and job.isAlive:
                        updateStatusHook('Conversion job is running. Aborting conversion task...')
                        updateStatusHook.func_dict['TERMINATE'] = True
                        time.sleep(1)
                root.destroy()

def logMessage(msg):
        if sys.stdout:
                print msg
        updateStatusHook(msg)

def quitApp(event):
        destroyApp()

def newFile():
        if tkMessageBox.askyesno("New", "Create new file?"):
                pass

def helpAbout():
        if tkMessageBox.showinfo("DSL2SQLite",
                                 "DSL2SQLite Converter\n\n"+
                                 "pyDictionary management utility\n" +
                                 "Import Lingvo DSL source files into SQLite database\n"+
                                 "For details, see http://pydictionary.googlecode.com\n"+
                                 "copywhite, xhuman(c) 2009"):
                pass

# def openFile():
        # f = tkFileDialog.askopenfile(parent=root,mode='rb',filetypes=myFormats,title='Please select a file')
        # if f != None:
                # data = f.read()
                # f.close()
                # print "I got %d bytes from the file." % len(data)

# chars not allowed in table names
illegal_chars = u'-/\?=+<>:;"*|!@#$%^&*'

def replace_illegal_chars(raw):
        return ''.join([c in illegal_chars and '_' or c for c in raw])

def openFileName(event=None, file_formats=dslFormats):
        fname = tkFileDialog.askopenfilename(parent=root,filetypes=file_formats,title='Please select source file')
        if fname:
                logMessage('Source file: ' + fname)
                srcName.set(fname)
                barename = os.path.splitext(os.path.basename(fname))[0]
        if not destName.get() and len(fname) > 3:
                if not os.path.exists(db_dir):
                        os.mkdir(os.path.join(os.path.abspath(os.path.curdir), db_dir))

                ## infer database path from DSL source file name
                db_path = os.path.join(os.path.abspath(os.path.curdir),
                                       db_dir,
                                       barename + '.db',
                                       )
                destName.set(db_path)


        if barename:
                table.set(replace_illegal_chars(barename))
        return "break"

def openDestFileName(event=None):
#    global destName
        fname = tkFileDialog.askopenfilename(parent=root,
                                             filetypes=sqliteFormats,
                                             title='Please select target file')
        if fname:
                destName.set(fname)
                logMessage('Target file: ' + fname)
        else:
                logMessage('No target file selected ')

def setTitle():
        table_name = tkSimpleDialog.askstring('Set SQLite table name', 'table name')
        logMessage('Selected table:' + str(table_name))
        table.set(table_name)

def updateStatusHook(data=None, show_hint=True, show_log=True):
        global globalStatus
        if show_hint:
                statusVar.set(data[:45])

        if show_log:
                globalStatus = str(data)
                log.insert(END, str(data))
                log.insert(END, '\n')
                log.see('end')

        if globalStop:
                return True
        else:
                return False

def statusHint(hint):
#    global globalStatus
#    globalStatus = statusVar.get()
        updateStatusHook(hint[:45], show_hint=True, show_log=False)

def statusRevert():
#    global globalStatus
        if globalStatus:
                statusHint(globalStatus)
                # statusVar.set(globalStatus)

def saveFile():
        filename = tkFileDialog.asksaveasfilename(parent=root,filetypes=myFormats,title="Save image as...")
        if len(filename) > 0:
                print "Now saving as %s" % (filename)


def setColor():
#    global log
        col= tkColorChooser.askcolor()
        # workaround: on linux col[1] returns object, not string
        col2 = isinstance(col[1], str) and col[1] or col[1].string
        log.config(background=col2)
        statusHint('Color set to: ' + col2)

class ConverterThread(threading.Thread):
        def __init__(self, srcName, destName, table, callbackFunc, enkod):
                threading.Thread.__init__(self, name='converterThread')
                self.infile = srcName
                self.outfile = destName
                self.table = table
                self.callbackFunc = callbackFunc
                self.running = True
                self.enc = enkod
        def run(self):
                try:
                        import pyDSL2Sqlite
                except:
                        updateStatusHook('ERROR while importing pyDSL2Sqlite.py! Operation cancelled...')
                        sys.exit(-1)
                try:
                        pyDSL2Sqlite.convert_dsl(self.infile, self.outfile, self.table, self.callbackFunc, self.enc)
                except Exception, e:
                        updateStatusHook(str(e), True, True)

#        count = 0
#        while self.running:
#            count += 1
#            print "loop %d" % (count,)
#            self._stopevent.wait(self._sleepperiod)
                print 'Finished background job'

def srcReturnHadler(event=None):
        if not srcName.get() or srcName.get().startswith('Click'):
                openFileName()
        else:
                doConversion()

def doConversion(event=None):
        global globalStatus
        global job
        if job and job.isAlive():
                updateStatusHook("Job already in progress")
                return

        if not srcName.get() or srcName.get().startswith('Click'):
                globalStatus = 'No source file selected. Please select a source file first'
                logMessage(globalStatus)
                return
        if not destName.get():
                logMessage('No target file selected! Please select select the SQLite database file')
                return
        updateStatusHook.func_dict['TERMINATE'] = False

        logMessage('Starting import...')
        globalStatus = 'Importing...'
        updateStatusHook(globalStatus)
#    global srcName, destName,table,updateStatusHook,job
        job = ConverterThread(srcName.get(),
                              destName.get(),
                              table.get(),
                              updateStatusHook,
                              encVar.get())
        job.start()

def stopConversion(event=None):
        global job
        if job and job.isAlive():
                if tkMessageBox.askokcancel("Abort Operation",
                                            "Conversion has not finished yet. Are you sure you want to abort?"):
                        logMessage('Stopping import... Please wait while cleaning up...')
                        updateStatusHook.func_dict['TERMINATE'] = True
                        job = None
        else:
                logMessage('No job is running')


def rightClickHandler(event, menu):
        'shows a context menu on right-click'
        # create a menu
        # display the popup menu
##    try:
        menu.tk_popup(event.x_root, event.y_root, 0)
##    finally:
##        # make sure to release the grab (Tk 8.0a1 only)
##        popupMenu.grab_release()
##
#w.bind("<Button-3>", do_popup)

import traceback

#def formatExceptionInfo(maxTBlevel=5):
        #cla, exc, trbk = sys.exc_info()
        #excName = cla.__name__
        #try:
                #excArgs = exc.__dict__["args"]
        #except KeyError:
                #excArgs = ''
                #pass
        #excTb = traceback.format_tb(trbk, maxTBlevel)
        #return (excName, excArgs, excTb)
def daemonServerRunner():
        if 'win' in sys.platform:
                python_cmd = 'python.exe'
        else:
                python_cmd = 'python'

        subprocess.Popen(python_cmd + " webserver.py",
                         #executable=python_cmd,
                         shell=True)

def srvThreadInvoke():
        if srv_status == SRV_STATE_DOWN:
                d = threading.Thread(name='pywords_daemon', target=daemonServerRunner)
                d.setDaemon(True)
                d.start()
        else:
                updateStatusHook('Server already running')

def srvStart(event=None):
        try:
                if srv_status != SRV_STATE_RUNNING:

                        if srv_status == SRV_STATE_UNKNOWN:
                                ## allow for a delay in case of unknown state
                                root.after(SRV_POLL_PERIOD + 5, srvThreadInvoke)
                        ##
                        if srv_status == SRV_STATE_DOWN:
                                srvThreadInvoke();
                else:
                        updateStatusHook('Server already started')

        except Exception, e:
                updateStatusHook(e,True, True)
                updateStatusHook(traceback.format_stack(), False, True)

def srvStop():
        #import urllib
        updateStatusHook('Stopping the server not implemented yet')
        #urllib.urlopen('http://%s:%s/%s' % (srv_host, srv_port, 'quit'))

def launchBrowser(event=None):
        updateStatusHook('Launching browser at ' + SERVER_URL)
        webbrowser.open_new_tab(SERVER_URL)

#def updateServerStatus():
        #pass

def ping_server():
        print "checking...",
        #global pinger_thread
        global srvStatusVar
        global srv_status
        try:
                u = urllib.urlopen('http://' + srv_host + ":" + srv_port)
        except:
                srv_status = SRV_STATE_DOWN
                srvStatusVar.set('Server status: down')
                print 'srvStatus: down'
        else:
                srv_status = SRV_STATE_RUNNING
                srvStatusVar.set('Server status: running')
                print 'srvStatus: running'
        root.after(SRV_POLL_PERIOD, ping_server)

# Configure shortcuts
##root.bind("<Alt-KeyPress-n>", keyHandler)
root.bind("<Alt-KeyPress-x>", quitApp)
root.bind("<Alt-KeyPress-c>", doConversion)
root.bind("<Alt-KeyPress-o>", openFileName)
root.bind("<Control-KeyPress-o>", openFileName)

root.bind('<Alt-KeyPress-u>', lambda e:srcEntry.focus_set())
root.bind('<Alt-KeyPress-d>', lambda e:destEntry.focus_set())
root.bind('<Alt-KeyPress-s>', doConversion)
root.bind('<Alt-KeyPress-p>', stopConversion)

root.bind('<Alt-KeyPress-r>', srvStart)
root.bind('<Alt-KeyPress-l>', launchBrowser)


#root.bind('<KeyPress-Return>', doConversion )

root.bind("<Control-KeyPress-q>", quitApp)

menu = Menu(root)
root.config(menu=menu)
#root.title = "DSL Converter"

filemenu = Menu(menu, tearoff=0)
menu.add_cascade(label="File", menu=filemenu, underline=0)
##filemenu.add_command(label="New", command=newFile, underline=0)
filemenu.add_command(label="Open Source...", command=openFileName, underline=0)
filemenu.add_command(label="Select Target...", command=openDestFileName, underline=7)

##filemenu.add_command(label="Save...", command=saveFile, underline=0)
#filemenu.add_separator()
filemenu.add_command(label="Exit", command=destroyApp, underline=1)

editmenu = Menu(menu, tearoff=0)
menu.add_cascade(label="Edit", menu=editmenu, underline=0)
editmenu.add_command(label='Set table name', command=setTitle, underline=4)

boolVar = BooleanVar(editmenu,value=True)
editmenu.add_checkbutton(label="Drop table if exists",variable=boolVar, underline=0)

editmenu.add_command(label='Change color', command=setColor, underline=4)
editmenu.add_command(label='Clear log', command=lambda : log.delete(1.0, END), underline=0)


helpmenu = Menu(menu, tearoff=0)
menu.add_cascade(label="Help", menu=helpmenu, underline=0)
helpmenu.add_command(label="About...", command=helpAbout, underline=0)

# configBar
conf = LabelFrame(root, width=33, padx=17, pady=10)#, text='Server Control'
srvRunBtn = Button(conf,  text="Run Server", command=srvStart, underline=0)
srvRunBtn.bind('<Enter>', lambda e,hint='Start web server' : statusHint(hint) )
srvRunBtn.bind('<Leave>', lambda e : statusRevert() )
srvRunBtn.pack(side=LEFT, padx=22, pady=2)

srvStopBtn = Button(conf,  text="Stop Server", command=srvStop, underline=0)
srvStopBtn.bind('<Enter>', lambda e,hint='Stop web server' : statusHint(hint) )
srvStopBtn.bind('<Leave>', lambda e : statusRevert() )
srvStopBtn.pack(side=LEFT, padx=22, pady=2)

openBrowser = Button(conf,  text="Launch Browser", command=launchBrowser, underline=0)
openBrowser.bind('<Enter>', lambda e,hint='Open application in browser' : statusHint(hint) )
openBrowser.bind('<Leave>', lambda e : statusRevert() )

openBrowser.pack(side=LEFT, padx=22, pady=2)

srvStatusVar = StringVar(value='Server status: unknown')
srvStatus = Label(conf, textvariable=srvStatusVar)
srvStatus.pack(side=LEFT)
conf.pack()


#create a toolbar
toolbar = Frame(root, width=33, pady=15)

##b = Button(toolbar, text="new", width=5, command=newFile, underline=0)
##b.pack(side=LEFT, padx=2, pady=2)
##
##b = Button(toolbar, text="Open", width=5, command=openFileName, underline=0)
##b.pack(side=LEFT, padx=2, pady=2)

tableLabel = Label(toolbar, text='Database table: ')
tableLabel.pack(side=LEFT)

table = StringVar(toolbar,None)
tableEntry = Entry (toolbar, textvariable=table, width=17)
tableEntry.bind('<Enter>', lambda e : statusHint('Table name to create in the SQLite database') )
tableEntry.bind('<Leave>', lambda e : statusRevert() )
tableEntry.bind('<KeyPress-Return>', srcReturnHadler)

tableEntry.pack(side=LEFT)
convBtn = Button(toolbar,  text="Import", width=8, command=doConversion, underline=0)
convBtn.bind('<Enter>', lambda e,hint='Click to start importing data' : statusHint(hint) )
convBtn.bind('<Leave>', lambda e : statusRevert() )
convBtn.pack(side=LEFT, padx=12, pady=2)

#ENCODINGS = "utf16", "utf8", "latin1", "cp1251", "cp1252", "cp1253"
encVar = StringVar(toolbar)
encVar.set(encodings[0])
encodings = OptionMenu(toolbar, encVar, *encodings)


encodings.pack(side=LEFT)

stopBtn = Button(toolbar,  text="Abort", width=8, command=stopConversion, underline=3)
stopBtn.bind('<Enter>', lambda e,hint='Click to abort importing' : statusHint(hint) )
stopBtn.bind('<Leave>', lambda e : statusRevert() )
stopBtn.pack(side=LEFT, padx=12, pady=2)

quitBtn = Button(toolbar,  text="Exit", width=8, command=destroyApp, underline=1)
quitBtn.bind('<Enter>', lambda e,hint='Exit Program' : statusHint(hint) )
quitBtn.bind('<Leave>', lambda e : statusRevert() )
quitBtn.pack(side=LEFT, padx=12, pady=2)

toolbar.pack(side=TOP)

mid = Frame(root)

## SOURCE FRAME ################

srcFrame = Frame(mid, width=200, height=140)
srcLabel = Label(srcFrame, text="Source:", width=9, underline=2)
srcLabel.pack(side=LEFT, ipadx=1, padx=1)
srcName = StringVar(srcFrame, "Click button to select file...")


srcEntry = Entry(srcFrame,  textvariable=srcName, width=75)
# if default text in input box then clear it on focus
srcEntry.bind('<FocusIn>', lambda e : srcName.get().startswith('Click') and srcName.set(''))
srcEntry.bind('<Enter>', lambda e,hint='Path to source DSL file' : statusHint(hint) )
srcEntry.bind('<Leave>', lambda e : statusRevert() )

srcPopup = Menu(srcEntry, tearoff=0)
srcPopup.add_command(label="Open DSL Source", command=openFileName) # , command=next) etc...
srcPopup.add_separator()
srcPopup.add_command(label="Cancel", command=None) # , command=next) etc...

srcEntry.bind('<Button-3>', lambda e, menu=srcPopup : rightClickHandler(e, menu))

srcEntry.pack(side=LEFT, padx=5)
srcEntry.bind('<KeyPress-Return>', srcReturnHadler)

##srcPopup.add_command(label="Previous")
##srcPopup.add_separator()
##srcPopup.add_command(label="Home")


srcBtn = Button(srcFrame, text="...", width=2,pady=0, command=openFileName)
srcBtn.bind('<Enter>', lambda e : statusHint('Click to select file') )
srcBtn.bind('<Leave>', lambda e : statusRevert() )
srcBtn.pack(side=LEFT, padx=2, pady=2)

srcFrame.pack(side=TOP)

destFrame = Frame(mid,
                  width=300,
                  height=100,
                  relief=FLAT,
                  borderwidth=0)
destLabel =  Label(destFrame, text="SQLite DB:", width=9, underline=7)
destLabel.pack(side=LEFT, padx=1)
destName = StringVar(None, value='')
destEntry = Entry(destFrame,  textvariable=destName, width=75)
destEntry.bind('<Enter>', lambda e,hint='Path to the SQLite database file' :  statusHint(hint) )
destEntry.bind('<Leave>', lambda e : statusRevert() )
destEntry.bind('<KeyPress-Return>', doConversion )
# TODO: write handler
destPopup = Menu(destEntry, tearoff=0)
destPopup.add_command(label="Select SQLite database", command=openDestFileName) # , command=next) etc...
destPopup.add_separator()
destPopup.add_command(label="Cancel", command=None) # , command=next) etc...

destEntry.bind('<Button-3>', lambda e, menu=destPopup : rightClickHandler(e, menu))

destEntry.pack(side=LEFT, padx=5 )

destBtn = Button(destFrame,
                 text="...",
                 width=2,
                 pady=0,
                 command=openDestFileName)
destBtn.bind('<Enter>',
             lambda e, hint='Click to select file' : statusHint(hint) )
destBtn.bind('<Leave>', lambda e : statusRevert() )

destBtn.pack(side=LEFT, padx=2, pady=2)

destFrame.pack(side=BOTTOM)

mid.pack(padx=10,pady=10)
# status bar
statusVar = StringVar(root,globalStatus[:35])
status = Label(root,
               textvariable=statusVar,
               bd=1,
               relief=SUNKEN,
               anchor=W)
status.pack(side=BOTTOM, fill=X)

logVar = StringVar(root,globalStatus)

logFrame = Frame(root)

scrollbar = Scrollbar(logFrame)
scrollbar.pack(side=RIGHT, fill=Y)

log = Text(logFrame,
           relief=RIDGE,
           wrap=WORD,
           yscrollcommand=scrollbar.set,
#           background='#dcf8e6',
#           background='SystemButtonFace',
height=15)
#log.yview_pickplace('end')
#log.see('end')
scrollbar.config(command=log.yview)

logPopup = Menu(log, tearoff=0)
logPopup.add_command(label='Clear log', command=lambda : log.delete(1.0, END), underline=0)
logPopup.add_command(label='Copy', command=lambda : str, underline=0)
log.bind('<Button-3>', lambda e, menu=logPopup : rightClickHandler(e, menu))
log.pack()
logFrame.pack()

# poll to check if server is alive
root.after(SRV_POLL_PERIOD, ping_server)

root.mainloop()

